#pragma once
#include "CParser.h"

// This is the old new, which replaced the old old and is replaced by the new new, which is amazingly simplified and weirdly better
#if FALSE
enum eMathOperationType
{
	MATH_OP_NEGATE		= 1,
	MATH_OP_CREMENT,
	MATH_OP_DO_THING,
	MATH_OP_DO_THING_TO_THING,
	MATH_OP_COMPARE_THING_WITH_THING,
};

enum eMathOperation
{
	MATH_NEGATE			= 1,
	MATH_INCREMENT,
	MATH_DECREMENT,
	MATH_ADD_THING_TO_THING,
	MATH_SUB_THING_FROM_THING,
	MATH_SET,
	MATH_MULT,
	MATH_MULT_THING_BY_THING,
	MATH_DIV,
	MATH_DIV_THING_BY_THING,
	MATH_ADD,
	MATH_OPERATION_12,
	MATH_LESSTHAN,
	MATH_MORETHAN,
	MATH_IS_THING_GREATER_OR_EQUAL_TO_THING_1,
	MATH_IS_THING_GREATER_OR_EQUAL_TO_THING_2,
	MATH_CSET,
	MATH_ADD_THING_TIMED,
	MATH_SUB_THING_TIMED,
	MATH_ADD_THING_TO_THING_TIMED,
	MATH_SUB_THING_FROM_THING_TIMED,
	MATH_SUB
};

class CMathOperation
{
	eMathOperationType	m_eType;
	CToken			*	m_pOperator;
	CToken			*	m_pSubOperator;
	CToken			*	m_pOperation;
	CToken			*	m_pSubOperation;
	CToken			*	m_pExpressions[MAX_PARAMS];
	CToken			*	m_pLastToken;
	int					m_nNumExpressions;
	int					m_nNumOperators;
	int					m_nNumOperands;
	bool				m_bOperatorFirst : 1;
	bool				m_bLineHasOperator : 1;
	bool				m_bLineHasExpression : 1;
	bool				m_bLineHasMultiExpression : 1;

public:
	CMathOperation()
	{
		m_pOperator		= nullptr;
		m_pSubOperator	= nullptr;
		m_pOperation	= nullptr;
		m_pSubOperation	= nullptr;
		m_pLastToken	= nullptr;
		m_nNumExpressions = 0;
		m_nNumOperators	= 0;
		m_nNumOperands	= 0;

		m_bLineHasOperator = false;
		m_bLineHasExpression = false;
		m_bLineHasMultiExpression = false;
	}

	void AddExpression(CToken *pToken)
	{
		if(m_nNumExpressions >= MAX_PARAMS)
			throw("Too many expressions");

		switch(pToken->GetType())
		{
		case TOKEN_VAR:
		case TOKEN_VARINDEX:
			if(m_pLastToken && m_pLastToken->GetType() == TOKEN_VAR && m_pLastToken->GetVariableValue()->IsArray())
				m_nNumOperands--;	// this is an array index and is one and the same with the last operand

		case TOKEN_VALUE:
		case TOKEN_STRING:
			if(m_pLastToken)
			{
				if(
					m_pLastToken->GetType() != TOKEN_VAR ||
					!m_pLastToken->GetVariableValue()->IsArray() ||
					(
						pToken->GetType() != TOKEN_VARINDEX &&
						pToken->GetType() != TOKEN_VAR
					)
				)
				{
					if(m_pLastToken->GetType() != TOKEN_OPERATOR)
						THROW("Expected operator after '%s'", m_pLastToken->GetString());
				}
			}
			else m_bOperatorFirst = false;

			m_bLineHasExpression = true;
			m_nNumOperands++;
			break;
		case TOKEN_OPERATOR:
			if(m_pLastToken)
			{
				if(m_pLastToken->GetType() == TOKEN_OPERATOR)
					THROW("Expected value or variable after '%s'", m_pLastToken->GetString());
			}
			else m_bOperatorFirst = true;

			m_bLineHasOperator = true;
			m_nNumOperators++;
			break;
		default: throw("Invalid expression '%s'", pToken->GetString());
		}

		m_pExpressions[m_nNumExpressions++] = m_pLastToken = pToken;
	}

	int Process(CToken * pTokensOut[MAX_TOKENS], bool bRequireCondition)
	{
		int idx = 0;
		int nSize = 0;

		if(!m_bLineHasExpression)
			throw("No operands");
		if(!m_bLineHasOperator)
			throw("No operators");

		CToken		*pOpTok = nullptr,
					*pOpVal = nullptr,
					*pVarTok = nullptr,
					*pVarIdxTok = nullptr,
					*pValTok = nullptr,
					*pValIdxTok = nullptr;
		COperation	*pOperation = nullptr;
		const CTypeBase		*	Type,
							*	LType;

		bool		bNeedOperator		= false,
					bNeedOperand		= false,
					bNeedExpression		= false,
					bHaveAssignedVar	= false,
					bHaveBaseVar		= false,
					bHaveBaseOperator	= false,
					bHaveFullOperation	= false,
					bUsedBaseVar		= false,		// for size tracking of repeated var uses
					bMustEnd			= false;
		
		const CTypeBase		*	BaseType;

		CToken	*	pOperatorToken,
				*	pUnaryOperatorToken,
				*	pBaseOperatorToken,
				*	pBaseVarToken,
				*	pBaseVarIdxToken,
				*	pOperandToken,
				*	pCurVarIdxToken;

		for(int i = 0; i < m_nNumExpressions; i++)
		{
			// This is a string? Probably not, lets see if it's a constant or something...
			if(m_pExpressions[i]->GetType() == TOKEN_STRING)
				Parser.ValidateStringToken(m_pExpressions[i], LType->Standardise(), nullptr, -2);

			switch(m_pExpressions[i]->GetType())
			{
			case TOKEN_OPERATOR:
				if(bNeedOperand)
					THROW("Expected operand but found '%s'", m_pExpressions[i]->GetString());
				
				pOperatorToken = m_pExpressions[i];
				pUnaryOperatorToken = nullptr;
				
				if(bNeedOperator)
				{
					if(bHaveBaseOperator)
					{
						// This aint my first operation, Jack!
						bNeedOperand = true;

						// Make sure we dont end with an operator
						bHaveFullOperation = false;
					}
					else
					{
						// Check for post unary
						auto pOperator = pOperatorToken->GetOperatorValue();
						auto pOperation = bRequireCondition ? pOperator->GetConditionMatchingType(Type) : pOperator->GetOperationMatchingType(Type);

						if(!pOperation || pOperation->GetRightSizeLimit() < 0)
						{
							// Assign the smooth operator
							pBaseOperatorToken = pOperatorToken;
							bHaveBaseOperator = true;
							bNeedOperand = true;
						}
						else
						{
							// Operate the unary operator
							pOperatorToken->SetValue(pOperation);

							auto pValToken = new CToken(TOKEN_VALUE);
							pValToken->SetValueType(Type->GetBasicType());
							pValToken->SetValue(pOperation->GetRightSizeLimit());
							nSize += pValToken->GetValueSize();
						
							pTokensOut[idx++] = pOperatorToken;
							pTokensOut[idx++] = pBaseVarToken;
							if(pBaseVarIdxToken) pTokensOut[idx++] = pBaseVarIdxToken;
							pTokensOut[idx++] = pValToken;

							bHaveFullOperation = true;
							bMustEnd = true;
						}
					}

					bNeedOperator = false;
				}
				else
				{
					// Operator first? Looks like we got ourselves some incrementation...
					bNeedOperand = true;
					pUnaryOperatorToken = pOperatorToken;
				}
				break;

			case TOKEN_VALUE:
			case TOKEN_LABEL:
				// Check we havent started the line with a value (that would be odd)
				if(!bNeedOperand) throw("Constant value cannot be assigned to");

			case TOKEN_VAR:
			case TOKEN_VARINDEX:
				if(bNeedOperator) THROW("Expected operator but found '%s'", m_pExpressions[i]->GetString());

				pOperandToken = m_pExpressions[i];

				if(pOperandToken->GetType() == TOKEN_VAR && pOperandToken->GetVariableValue()->IsArray())
				{
					if(i < m_nNumExpressions && (m_pExpressions[i+1]->GetType() == TOKEN_VAR || m_pExpressions[i+1]->GetType() == TOKEN_VARINDEX))
					{
						pCurVarIdxToken = m_pExpressions[++i];
					}
					else
					{
						//Warning("Array missing its index, compiling as variable");
						pCurVarIdxToken = nullptr;
					}
				}
				else pCurVarIdxToken = nullptr;

				Type = pOperandToken->GetArgType();

				if(bNeedOperand)
				{
					if(pUnaryOperatorToken)
					{
						// Unary operation (pre)
						auto pOperator = pUnaryOperatorToken->GetOperatorValue();
						auto pOperation = bRequireCondition ? pOperator->GetConditionMatchingType(Type) : pOperator->GetOperationMatchingType(Type);

						if(!pOperation)
						{
							if(bRequireCondition) THROW("Couldnt find unary condition matching type '%s'", Type->GetName());
							else THROW("Couldnt find unary operation matching type '%s'", Type->GetName());
						}

						pUnaryOperatorToken->SetValue(pOperation);

						auto pValToken = new CToken(TOKEN_VALUE);
						pValToken->SetValueType(Type->GetBasicType());
						pValToken->SetValue(pOperation->GetRightSizeLimit());
						nSize += pValToken->GetValueSize();
						
						pTokensOut[idx++] = pUnaryOperatorToken;
						pTokensOut[idx++] = pOperandToken;
						if(pCurVarIdxToken) pTokensOut[idx++] = pCurVarIdxToken;
						pTokensOut[idx++] = pValToken;

						bHaveFullOperation = true;
						bMustEnd = true;
					}
					else
					{
						// a = b, a += b, etc..
						auto pOperator = pOperatorToken->GetOperatorValue();
						auto pOperation = bRequireCondition ?	pOperator->GetConditionMatchingTypes(BaseType, Type):
																pOperator->GetOperationMatchingTypes(BaseType, Type);

						if(!pOperation)
						{
							if(bRequireCondition) THROW("Couldnt find condition matching types '%s' and '%s'", BaseType->GetName(), Type->GetName());
							else THROW("Couldnt find operation matching types '%s' and '%s'", BaseType->GetName(), Type->GetName());
						}
						
						// Convert types
						if(BaseType->IsFloat() && Type->IsInt())
						{
							Type = CSyntax::GetType(TYPE_FLOAT);
							pOperandToken->SetValueType(PT_FLOAT);
							pOperandToken->SetValue((float)pOperandToken->GetIntValue());
						}
						else if(BaseType->IsInt() && Type->IsFloat())
						{
							Type = CSyntax::GetType(TYPE_INT);
							pOperandToken->SetValueType(PT_INT);
							pOperandToken->SetValue((int)pOperandToken->GetFloatValue());
							Parser.Warning("Conversion from float to int (possible loss of data)");
						}

						pOperatorToken->SetValue(pOperation);
						pTokensOut[idx++] = pOperatorToken;
						pTokensOut[idx++] = pBaseVarToken;
						if(pBaseVarIdxToken) pTokensOut[idx++] = pBaseVarIdxToken;
						pTokensOut[idx++] = pOperandToken;

						// Uses of base var ++
						if(bUsedBaseVar) nSize += pBaseVarToken->GetValueSize();
						else bUsedBaseVar = true;
						bHaveFullOperation = true;
					}

					bNeedOperator = true;
					bNeedOperand = false;
				}
				else
				{
					// We've encountered a var first...
					bNeedOperator = true;
					bHaveBaseVar = true;
					pBaseVarToken = pOperandToken;
					BaseType = Type;

					if(pCurVarIdxToken) pBaseVarIdxToken = pCurVarIdxToken;
					else pBaseVarIdxToken = nullptr;
				}
				break;

			default:
				if(!bHaveFullOperation)
				{
					if(bNeedOperator) throw("Expected operator");
					if(bNeedOperand) throw("Expected operand");
					throw("No operations");
				}
				break;
			}
		}
		return nSize;
	}

	COperation * GetOperation(COperator *pOperator, CToken *pOutVar)
	{
		if(!pOperator->IsAssignment()) THROW("Nothing assigned in operation '%.3s'", pOperator->GetOperatorString());
		return pOperator->GetOperationMatchingTypes(pOutVar->GetArgType(), CSyntax::GetType(TYPE_INT));
	}

	inline void SetType(eMathOperationType type)		{m_eType = type;}
	inline eMathOperationType GetType()					{return m_eType;}
};
#endif